from NodeGraphQt import (
    NodeGraph,
    PropertiesBinWidget,
    NodesTreeWidget,
    NodesPaletteWidget
)

from PySide6 import QtWidgets, QtCore

from NodeGraphQt import BaseNode, BaseNodeCircle, NodeBaseWidget, constants, widgets
import autoflow.actionmanager
import autoflow.flowmanager
import copy
import json
import uuid
        
g_AllFlowActionitems = []

class NodeIntSpinBoxEdit(NodeBaseWidget):
    """
    Displays as a ``QLineEdit`` in a node.

    .. inheritance-diagram:: NodeGraphQt.widgets.node_widgets.NodeLineEdit
        :parts: 1

    .. note::
        `To embed a` ``QLineEdit`` `in a node see func:`
        :meth:`NodeGraphQt.BaseNode.add_text_input`
    """

    def __init__(self, parent=None, name='', label='', text='', placeholder_text=''):
        super(NodeIntSpinBoxEdit, self).__init__(parent, name, label)
        bg_color = constants.ViewerEnum.BACKGROUND_COLOR.value
        text_color = tuple(map(lambda i, j: i - j, (255, 255, 255),
                               bg_color))
        text_sel_color = text_color
        style_dict = {
            'QSpinBox': {
                'background': 'rgba({0},{1},{2},20)'.format(*bg_color),
                'border': '1px solid rgb({0},{1},{2})'
                          .format(*constants.ViewerEnum.GRID_COLOR.value),
                'border-radius': '3px',
                'color': 'rgba({0},{1},{2},150)'.format(*text_color),
                'selection-background-color': 'rgba({0},{1},{2},100)'
                                              .format(*text_sel_color),
            }
        }
        stylesheet = ''
        for css_class, css in style_dict.items():
            style = '{} {{\n'.format(css_class)
            for elm_name, elm_val in css.items():
                style += '  {}:{};\n'.format(elm_name, elm_val)
            style += '}\n'
            stylesheet += style
        ledit = QtWidgets.QSpinBox()
        #ledit.setText(text)
        #ledit.setPlaceholderText(placeholder_text)
        ledit.setStyleSheet(stylesheet)
        ledit.setAlignment(QtCore.Qt.AlignCenter)
        ledit.editingFinished.connect(self.on_value_changed)
        ledit.clearFocus()
        self.set_custom_widget(ledit)
        self.widget().setMaximumWidth(140)

    @property
    def type_(self):
        return 'IntSpinBoxEditNodeWidget'

    def get_value(self):
        """
        Returns the widgets current text.

        Returns:
            str: current text.
        """
        return str(self.get_custom_widget().text())

    def set_value(self, num=0):
        """
        Sets the widgets current text.

        Args:
            text (str): new text.
        """
        if num != self.get_value():
            self.get_custom_widget().setValue(num)
            self.on_value_changed()

class NodeFloatSpinBoxEdit(NodeBaseWidget):
    """
    Displays as a ``QLineEdit`` in a node.

    .. inheritance-diagram:: NodeGraphQt.widgets.node_widgets.NodeLineEdit
        :parts: 1

    .. note::
        `To embed a` ``QLineEdit`` `in a node see func:`
        :meth:`NodeGraphQt.BaseNode.add_text_input`
    """

    def __init__(self, parent=None, name='', label='', text='', placeholder_text=''):
        super(NodeFloatSpinBoxEdit, self).__init__(parent, name, label)
        bg_color = constants.ViewerEnum.BACKGROUND_COLOR.value
        text_color = tuple(map(lambda i, j: i - j, (255, 255, 255),
                               bg_color))
        text_sel_color = text_color
        style_dict = {
            'QSpinBox': {
                'background': 'rgba({0},{1},{2},20)'.format(*bg_color),
                'border': '1px solid rgb({0},{1},{2})'
                          .format(*constants.ViewerEnum.GRID_COLOR.value),
                'border-radius': '3px',
                'color': 'rgba({0},{1},{2},150)'.format(*text_color),
                'selection-background-color': 'rgba({0},{1},{2},100)'
                                              .format(*text_sel_color),
            }
        }
        stylesheet = ''
        for css_class, css in style_dict.items():
            style = '{} {{\n'.format(css_class)
            for elm_name, elm_val in css.items():
                style += '  {}:{};\n'.format(elm_name, elm_val)
            style += '}\n'
            stylesheet += style
        ledit = QtWidgets.QDoubleSpinBox()
        #ledit.setText(text)
        #ledit.setPlaceholderText(placeholder_text)
        ledit.setStyleSheet(stylesheet)
        ledit.setAlignment(QtCore.Qt.AlignCenter)
        ledit.editingFinished.connect(self.on_value_changed)
        ledit.clearFocus()
        self.set_custom_widget(ledit)
        self.widget().setMaximumWidth(140)

    @property
    def type_(self):
        return 'IntSpinBoxEditNodeWidget'

    def get_value(self):
        """
        Returns the widgets current text.

        Returns:
            str: current text.
        """
        return str(self.get_custom_widget().text())

    def set_value(self, num=0.0):
        """
        Sets the widgets current text.

        Args:
            text (str): new text.
        """
        if num != self.get_value():
            self.get_custom_widget().setValue(num)
            self.on_value_changed()

class GraphNodeBase(BaseNode):

    def __init__(self):
        super().__init__()
        self._uuid = ""
        self._logicnode = None

    def load(self, node):
        self._uuid = node._uuid
        self.set_pos(node._pos[0], node._pos[1])
        self._logicnode = copy.deepcopy(node)
        self.onLoad(node)

    def onLoad(self, node):
        pass

    def save(self):
        d = {
            "Type": self._logicnode._type,
            "Action": self._logicnode._action,
            "Pos": self.pos(),
            "Out": "",
            "Break": "",
            "True": "",
            "False": "",
            "Param": "",
        }
        # Out pin
        try:
            outputport = self.output(0)
            for node_id in outputport.model.connected_ports.keys():
                node = self.graph.get_node_by_id(node_id)
                d["Out"] = node._uuid
                break
        except:
            DebugPrint("output pin 0 not found.")
        return self.onSave(d)

    def onSave(self, saveddata: dict):
        return saveddata

class FlowNode(GraphNodeBase):

    # unique node identifier.
    __identifier__ = 'nodes.basic'

    # initial default node name.
    NODE_NAME = 'Flow'

    def __init__(self):
        super(FlowNode, self).__init__()

class ActionNode(GraphNodeBase):

    # unique node identifier.
    __identifier__ = 'nodes.basic'

    # initial default node name.
    NODE_NAME = 'Action'

    def __init__(self):
        super(ActionNode, self).__init__()

    def onSave(self, saveddata):
        outputport = self.output(0)
        for node_id in outputport.model.connected_ports.keys():
            node = self.graph.get_node_by_id(node_id)
            saveddata["Out"] = node._uuid
            break
        return super().onSave(saveddata)

class LoopNode(GraphNodeBase):
    
    # unique node identifier.
    __identifier__ = 'nodes.internal'

    # initial default node name.
    NODE_NAME = 'LoopNode'

    def __init__(self):
        super(LoopNode, self).__init__()
        self.set_color(23, 150, 0)
        self.add_input("Execute", multi_input = True)
        self.add_combo_menu("Select", label="Action/Flow", items=("未选择Action/Flow",))
        combo: QtWidgets.QComboBox = self.get_widget("Select").get_custom_widget()
        combo.setFixedWidth(combo.width())
        for name, uid in g_AllFlowActionitems:
            combo.addItem(name, uid)
        self.add_custom_widget(NodeIntSpinBoxEdit(name="Times", parent=self.view, label="Times"))
        self.add_output("finished")
        self.add_output("break")

    def onLoad(self, node):
        times = node._times
        self.get_widget("Times").set_value(times)
        combo: QtWidgets.QComboBox = self.get_widget("Select").get_custom_widget()
        for i in range(combo.count()):
            if node._action == combo.itemData(i, QtCore.Qt.ItemDataRole.UserRole):
                combo.setCurrentIndex(i)
    
    def onSave(self, saveddata):
        combo: QtWidgets.QComboBox = self.get_widget("Select").get_custom_widget()
        actiondata = combo.currentData(QtCore.Qt.ItemDataRole.UserRole)
        # Out pin
        outputport = self.output(0)
        for node_id in outputport.model.connected_ports.keys():
            node = self.graph.get_node_by_id(node_id)
            saveddata["Out"] = node._uuid
            break
        # break pin
        outputport = self.output(1)
        for node_id in outputport.model.connected_ports.keys():
            node = self.graph.get_node_by_id(node_id)
            saveddata["Break"] = node._uuid
            break
        saveddata["Action"] = actiondata
        saveddata["Param"] = int(self.get_widget("Times").get_value())
        return super().onSave(saveddata)

class SleepNode(GraphNodeBase):
    
    # unique node identifier.
    __identifier__ = 'nodes.internal'

    # initial default node name.
    NODE_NAME = 'SleepNode'

    def __init__(self):
        super(SleepNode, self).__init__()
        self.set_color(200, 100, 100)
        self.add_input("sleep", multi_input = True)
        self.add_output("wakeup")
        self.add_custom_widget(NodeFloatSpinBoxEdit(name="Delay", parent=self.view, label="Second:"))

    def onLoad(self, node):
        delay = node._delay
        self.get_widget("Delay").set_value(delay)
    
    def onSave(self, saveddata):
        saveddata["Param"] = float(self.get_widget("Delay").get_value())
        return super().onSave(saveddata)

class ConditionNode(GraphNodeBase):
    
    # unique node identifier.
    __identifier__ = 'nodes.internal'

    # initial default node name.
    NODE_NAME = 'ConditionNode'

    def __init__(self):
        super(ConditionNode, self).__init__()
        self._uuid = ""
        self.set_color(204, 24, 240)
        self.add_input("check", multi_input = True)
        self.add_combo_menu("Select", label="Action/Flow", items=("未选择Action/Flow",))
        combo: QtWidgets.QComboBox = self.get_widget("Select").get_custom_widget()
        combo.setFixedWidth(combo.width())
        for name, uid in g_AllFlowActionitems:
            combo.addItem(name, uid)
        self.add_output("true")
        self.add_output("false")
    
    def onSave(self, saveddata):
        combo: QtWidgets.QComboBox = self.get_widget("Select").get_custom_widget()
        actiondata = combo.currentData(QtCore.Qt.ItemDataRole.UserRole)
        # true pin
        outputport = self.output(0)
        for node_id in outputport.model.connected_ports.keys():
            node = self.graph.get_node_by_id(node_id)
            saveddata["True"] = node._uuid
            break
        # false pin
        outputport = self.output(1)
        for node_id in outputport.model.connected_ports.keys():
            node = self.graph.get_node_by_id(node_id)
            saveddata["False"] = node._uuid
            break
        saveddata["Action"] = actiondata
        return super().onSave(saveddata)
    
    def onLoad(self, node):
        combo: QtWidgets.QComboBox = self.get_widget("Select").get_custom_widget()
        for i in range(combo.count()):
            if node._action == combo.itemData(i, QtCore.Qt.ItemDataRole.UserRole):
                combo.setCurrentIndex(i)
        return super().onLoad(node)

class BreakLoopNode(GraphNodeBase):
    
    # unique node identifier.
    __identifier__ = 'nodes.internal'

    # initial default node name.
    NODE_NAME = 'BreakLoopNode'

    def __init__(self):
        super(BreakLoopNode, self).__init__()
        self._uuid = ""
        self.set_color(204, 24, 240)
        self.add_input("execute", multi_input = True)

class PrintNode(GraphNodeBase):
    
    # unique node identifier.
    __identifier__ = 'nodes.internal'

    # initial default node name.
    NODE_NAME = 'PrintNode'

    def __init__(self):
        super(PrintNode, self).__init__()
        self.set_color(0, 75, 90)
        self.add_input(">", multi_input = True)
        self.add_text_input("Text", label="Text:")

    def onLoad(self, node):
        self.get_widget("Text").set_value(node._txt)
        return super().onLoad(node)

    def onSave(self, savedata):
        lineedit: QtWidgets.QComboBox = self.get_widget("Text").get_custom_widget()
        txt = lineedit.text()
        savedata["Param"] = txt
        return super().onSave(savedata)
    
def createEntryNode(graph: NodeGraph):
    node = graph.create_node("nodes.basic.FlowNode", name="Entry", color=(145, 3, 100))
    node.add_output("out")
    return node

def createFlowNode(graph: NodeGraph, name: str):
    node = graph.create_node("nodes.basic.FlowNode", name=name, color=(0, 181, 222))
    node.add_input(">", multi_input = True)
    node.add_output("out")
    return node

def createActionNode(graph: NodeGraph, name: str):
    node = graph.create_node("nodes.basic.ActionNode", name=name, text_color='#aeab20')
    node.add_input("execute", multi_input = True)
    node.add_output("out")
    return node

def createInternalNode(graph: NodeGraph, name: str, mode: str):
    if "N_IF" == mode:
        node = graph.create_node("nodes.internal.ConditionNode", name=name, text_color=(255, 255, 255))
        return node
    elif "N_FOR" == mode:
        name = "Loop"
        node = graph.create_node("nodes.internal.LoopNode", name=name, text_color='#faeb20')
        return node
    elif "N_SLEEP" == mode:
        node = graph.create_node("nodes.internal.SleepNode", name=name, text_color='#faeb20')
        return node
    elif "N_BREAKLOOP" == mode:
        node = graph.create_node("nodes.internal.BreakLoopNode", name=name, text_color='#faeb20')
        return node
    elif "N_PRINT" == mode:
        node = graph.create_node("nodes.internal.PrintNode", name=name)
        return node
    else:
        return None

def GetActionName(actid):
    name = autoflow.actionmanager.g_ActMgr.GetActionName(actid)
    if "Unknow" == name:
        name = autoflow.flowmanager.g_FlowManager.getFlowName(actid)
    return name


class FlowCanvas:

    def __init__(self, name = "", parent: QtWidgets.QWidget = None):
        self._name = name
        self._graph = NodeGraph(parent=parent)
        self._graph.register_nodes([
            FlowNode,
            ActionNode,
            LoopNode,
            ConditionNode,
            SleepNode,
            BreakLoopNode,
            PrintNode,
        ])
        self._graph.save_session
        self._filepath = ""

    def resize(self, w, h):
        self._graph.widget.resize(w, h)

    def show(self):
        self._graph.widget.show()

    def widget(self):
        return self._graph.widget

    def addActionToCanvas(self, actid: str, x: int, y: int, acttype: str):
        info = {
            "Type": acttype,
            "Action": actid,
        }
        import autoflow.flowmanager
        uid = str(uuid.uuid1()).replace("-", "")
        DebugPrint(f"new node {uid}")
        cls = autoflow.flowmanager.getNodeCls(acttype)
        node = cls(None, uid, info)
        node._pos = (x, y)
        self.addNodeToCanvas(node)

    def addNodeToCanvas(self, node):
        gnode = None
        if "Entry" == node._type:
            gnode = createEntryNode(self._graph)
        elif "N_IF" == node._type:
            name = "if"#GetActionName(node._action)
            gnode = self._graph.create_node("nodes.internal.ConditionNode", name=name, text_color=(255, 255, 255))
        elif "ACTION" == node._type:
            name = GetActionName(node._action)
            gnode = createActionNode(self._graph, name)
        elif "N_FOR" == node._type:
            name = "loop"#GetActionName(node._action)
            gnode = self._graph.create_node("nodes.internal.LoopNode", name=name, text_color=(0, 0, 0))
        elif "N_SLEEP" == node._type:
            name = "sleep"
            gnode = self._graph.create_node("nodes.internal.SleepNode", name=name, text_color='#faeb20')
        elif "N_BREAKLOOP" == node._type:
            name = "break"
            gnode = self._graph.create_node("nodes.internal.BreakLoopNode", name=name, text_color='#faeb20')
        elif "N_PRINT" == node._type:
            name = "print"
            gnode = self._graph.create_node("nodes.internal.PrintNode", name=name)
        if gnode:
            gnode.load(node)
        return gnode

    def loadFlowConfig(self, filepath: str):
        self._filepath = filepath
        import autoflow.flowmanager
        flowobj = autoflow.flowmanager.FlowBase(filepath)
        self._name = flowobj._name
        nodes = {}
        # 先恢复所有的节点出来
        for nodeid, n in flowobj._nodes.items():
            node: autoflow.flowmanager.ExecuteNode = n
            gnode = self.addNodeToCanvas(node)
            if gnode:
                nodes[nodeid] = gnode
        # 恢复连线
        for uid, gnode in nodes.items():
            node = flowobj.getNode(uid)
            gnode = nodes[uid]
            if node._type in ("Entry", "ACTION", "N_SLEEP", "N_PRINT"):
                if node._out:
                    targetgnode: BaseNode = nodes[node._out]
                    targetport = targetgnode.input(0)
                    gnode.set_output(0, targetport)
            elif "N_IF" == node._type:
                if node._true:
                    targetgnode: BaseNode = nodes[node._true]
                    targetport = targetgnode.input(0)
                    gnode.set_output(0, targetport)
                if node._false:
                    targetgnode: BaseNode = nodes[node._false]
                    targetport = targetgnode.input(0)
                    gnode.set_output(1, targetport)
            elif "N_FOR" == node._type:
                if node._out:
                    targetgnode: BaseNode = nodes[node._out]
                    targetport = targetgnode.input(0)
                    gnode.set_output(0, targetport)
                if node._break:
                    targetgnode: BaseNode = nodes[node._break]
                    targetport = targetgnode.input(0)
                    gnode.set_output(1, targetport)
        self._graph._undo_stack.setClean()
        self._graph._undo_stack.clear()
        self._graph._undo_stack.cleanChanged.connect(self.onModify)
        self._graph.context_menu_prompt.connect(self.onMenuPrompt)

    def Save(self):
        if self._graph._undo_stack.isClean():
            DebugPrint("没东西可以保存", self._graph._undo_stack.cleanIndex())
            return
        DebugPrint("存盘")
        self._graph._undo_stack.setClean()
        graph = self._graph
        savedata = {
            "Name": self._name,
            "Flow": {

            }
        }
        for gnode in graph.all_nodes():
            d = gnode.save()
            savedata["Flow"][gnode._uuid] = d
        f = open(self._filepath, "w")
        f.write(json.dumps(savedata))
        f.close()
        #DebugPrint(savedata)

    def onModify(self):
        DebugPrint("有改动")

    def onMenuPrompt(self, menu, node):
        DebugPrint(menu, node)

def InitConfig():
    global g_AllFlowActionitems
    if g_AllFlowActionitems:
        return
    autoflow.flowmanager.g_FlowManager.loadAllFlows()
    autoflow.actionmanager.g_ActMgr.LoadAllActions()
    allflows = autoflow.flowmanager.g_FlowManager.getAllFlows()
    allacts = autoflow.actionmanager.g_ActMgr.GetAllActions()
    g_AllFlowActionitems = allflows + allacts


def Test():
    canvas = FlowCanvas()
    canvas.resize(1100, 800)
    canvas.show()

    def cc():
        canvas.Save()

    btn = QtWidgets.QPushButton()
    btn.clicked.connect(cc)
    btn.show()
    canvas.btn = btn

    InitConfig()
    #node1 = createFlowNode(graph, name="TestFlowNode111")
    #node2 = createFlowNode(graph, name="TestFlowNode222")
    #node3 = createInternalNode(graph, name="判断日常界面是否打开", mode="N_IF")
    #node4 = createInternalNode(graph, name="判断日常界面是否打开", mode="N_FOR")
    #node4 = createInternalNode(graph, name="睡眠", mode="N_SLEEP")
    #node4 = createInternalNode(graph, name="中断", mode="N_BREAKLOOP")
    #node4 = createInternalNode(graph, name="日志", mode="N_PRINT")
    #node4 = createActionNode(graph, name="action")

    canvas.loadFlowConfig("./flows/flow901d-ea6c-4004-9231-cdfc2f397223.json")

def Main():

    app = QtWidgets.QApplication([])

    Test()
    return app.exec_()

if "__main__" == __name__:
    Main()
